package felix.rule.service.actors.app;

import felix.actor.core.TActor;
import felix.actor.core.TActorId;
import felix.actor.core.TActorRef;
import felix.actor.core.msg.TActorMsg;
import felix.actor.core.msg.TMsgType;
import felix.rule.data.msg.QueueToRuleEngineMsg;
import felix.rule.data.msg.RuleEngineException;
import felix.rule.data.plugin.ComponentLifecycleEvent;
import felix.rule.data.plugin.ComponentLifecycleMsg;
import felix.rule.service.RuleChainService;
import felix.rule.service.actors.ActorSystemContext;
import felix.rule.service.actors.ContextAwareActor;
import felix.rule.service.actors.ContextBasedCreator;
import felix.rule.service.actors.TEntityActorId;
import felix.rule.service.actors.rule.RuleActor;
import felix.rule.service.impl.DefaultActorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.util.List;

import static felix.rule.data.msg.MessageType.*;


/**
 * 应用入口
 *
 * @author Felix
 * @date 2020/8/24
 */
@Slf4j
public class AppActor extends ContextAwareActor {

    private static final String MAIN_APP_ACTOR_ID = "mainAppActor";

    private boolean ruleChainsInitialized;
    private final RuleChainService ruleChainService;

    private AppActor(ActorSystemContext systemContext) {
        super(systemContext);
        ruleChainService = systemContext.getRuleChainService();
    }

    @Override
    protected boolean doProcess(TActorMsg msg) {
        if (!ruleChainsInitialized) {
            initRulesActors();
            ruleChainsInitialized = true;
            if (msg.getMsgType() != APP_INIT_MSG) {
                log.warn("Rule Chains initialized by unexpected message: {}", msg);
            }
        }
        TMsgType msgType = msg.getMsgType();

        if (APP_INIT_MSG.equals(msgType)) {
            return false;
        } else if (QUEUE_TO_RULE_ENGINE_MSG.equals(msgType)) {
            onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);
        } else if (COMPONENT_LIFE_CYCLE_MSG.equals(msgType)) {
            onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
        } else {
            return false;
        }
        return true;
    }

    private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
        TActorRef target = null;
        if (MAIN_APP_ACTOR_ID.equals(msg.getRuleId())) {
            log.warn("Message has system main id: {}", msg);
        } else {
            if (msg.getType().equals(AppActor.class.getSimpleName())
                    && msg.getEvent() == ComponentLifecycleEvent.DELETED) {
                log.info("[{}] Handling rule deleted notification: {}", msg.getRuleId(), msg);
                ctx.stop(new TEntityActorId(msg.getRuleId(), AppActor.class.getSimpleName()));
            } else {
                target = checkOrCreateRuleActor(msg.getRuleId());
            }
        }
        if (target != null) {
            target.tellWithHighPriority(msg);
        } else {
            log.debug("[{}] Invalid component lifecycle msg: {}", msg.getRuleId(), msg);
        }
    }

    private void initRulesActors() {
        log.info("Starting main system actor.");
        try {
            List<String> ruleIds = ruleChainService.findRuleIds();
            for (String ruleId : ruleIds) {
                log.debug("[{}] Creating rule actor", ruleId);
                TActorRef ref = checkOrCreateRuleActor(ruleId);
                if (ref != null) {
                    log.debug("[{}] Tenant actor created.", ruleId);
                }
            }
            log.info("Main system actor started.");
        } catch (Exception e) {
            log.warn("Unknown failure", e);
        }
    }

    private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
        if (StringUtils.isEmpty(msg.getRuleId())) {
            msg.getEngineMsg().getCallback().onFailure(new RuleEngineException("Message not has system rule id!"));
        } else {
            TActorRef ref = checkOrCreateRuleActor(msg.getRuleId());
            if (ref != null) {
                ref.tell(msg);
            } else {
                msg.getEngineMsg().getCallback().onFailure(new RuleEngineException("cant find ruleId [" + msg.getRuleId() + "]"));

            }
        }
    }

    private TActorRef checkOrCreateRuleActor(String ruleId) {
        Integer count = ruleChainService.findCountRuleById(ruleId);
        if (count > 0) {
            return ctx.getOrCreateChildActor(new TEntityActorId(ruleId, AppActor.class.getSimpleName()),
                    () -> DefaultActorService.RULE_DISPATCHER_NAME,
                    () -> new RuleActor.ActorCreator(systemContext, ruleId));
        }
        return null;
    }

    public static class ActorCreator extends ContextBasedCreator {

        public ActorCreator(ActorSystemContext context) {
            super(context);
        }

        @Override
        public TActorId createActorId() {
            return new TEntityActorId(MAIN_APP_ACTOR_ID, AppActor.class.getSimpleName());
        }

        @Override
        public TActor createActor() {
            return new AppActor(context);
        }
    }

}
